/****************************************************************************
 * Copyright (c) 2008, 2009 Andreas Unger and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Andreas Unger - initial API and implementation 
 ****************************************************************************/

package org.eclipselabs.damos.execution.executionmodel.builder;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.eclipselabs.damos.dml.Fragment;
import org.eclipselabs.damos.execution.executionmodel.DataFlowTargetEnd;
import org.eclipselabs.damos.execution.executionmodel.ExecutionModel;
import org.eclipselabs.damos.execution.executionmodel.ExecutionModelFactory;
import org.eclipselabs.damos.execution.executionmodel.Link;
import org.eclipselabs.damos.execution.executionmodel.Node;

/**
 * @author Andreas Unger
 *
 */
public class ExecutionModelBuilder {

	public ExecutionModel build(Fragment fragment) throws DeadlockException {
		return new Context(fragment).execute();
	}

	private static class Context {

		private Fragment topLevelFragment;
		
		private ExecutionModel model;

		private Set<Node> nodes = new HashSet<Node>();
		
		public Context(Fragment topLevelFragment) {
			this.topLevelFragment = topLevelFragment;
		}
	
		public ExecutionModel execute() throws DeadlockException {
			model = ExecutionModelFactory.eINSTANCE.createExecutionModel();
			model.setTopLevelFragment(topLevelFragment);
			
			FlattenerHelper flattenerHelper = new FlattenerHelper(model);
			flattenerHelper.flatten();
			List<Node> backlog = new LinkedList<Node>(flattenerHelper.getNodes());

			boolean changed;
			do {
				changed = false;
				for (Iterator<Node> it = backlog.iterator(); it.hasNext();) {
					Node node = it.next();
					List<Node> drivingNodes = getDrivingNodes(node);
					if (drivingNodes.isEmpty()) {
						addDrivingNodeToExecutionModel(node);
						it.remove();
						changed = true;
					} else if (drivingNodesInExecutionModel(drivingNodes)) {
						addDrivenNodeToExecutionModel(node, drivingNodes);
						it.remove();
						changed = true;
					}
				}
			} while (changed);

			if (!backlog.isEmpty()) {
				throw new DeadlockException(backlog);
			}
			
			return model;
		}
		
		private List<Node> getDrivingNodes(Node node) {
			List<Node> drivingNodes = new ArrayList<Node>();
			for (DataFlowTargetEnd targetEnd : node.getIncomingDataFlows()) {
				if (targetEnd.getPort().getInput().isDirectFeedthrough()) {
					drivingNodes.add(targetEnd.getDataFlow().getSourceEnd().getNode());
				}
			}
			return drivingNodes;
		}
		
		private boolean drivingNodesInExecutionModel(List<Node> drivingNodes) {
			for (Node drivingNode : drivingNodes) {
				if (!nodes.contains(drivingNode)) {
					return false;
				}
			}
			return true;
		}
	
		private void addDrivingNodeToExecutionModel(Node node) throws DeadlockException {
			model.getNodes().add(node);
			model.getInitialNodes().add(node);
			nodes.add(node);
		}
		
		private void addDrivenNodeToExecutionModel(Node node, List<Node> drivingNodes) throws DeadlockException {
			model.getNodes().add(node);
	
			for (Node drivingNode : drivingNodes) {
				Link link = ExecutionModelFactory.eINSTANCE.createLink();
				model.getLinks().add(link);
				link.setSourceNode(drivingNode);
				link.setTargetNode(node);
			}
			
			nodes.add(node);
		}
		
	}
	
}
